Skip to content

Conversation

cminn10
Copy link

@cminn10 cminn10 commented Oct 5, 2025

Description

This PR fixes an issue where layout.title.subtitle does not properly clear/remove from the chart when subtitle object is not in place, or subtitle.text set to null, empty string, or whitespace-only values via Plotly.relayout().

Problem

When users attempted to clear a subtitle using any of these methods:

Plotly.relayout(gd, {'title.subtitle.text': null});
Plotly.relayout(gd, {'title.subtitle.text': ''});
Plotly.relayout(gd, {'title.subtitle.text': '   '});
Plotly.relayout(gd, {'title.subtitle': undefined});

The subtitle would remain visible on the chart. The only workaround was to set it to transparent:

Plotly.relayout(gd, {
    'title.subtitle.text': 'no text',
    'title.subtitle.font.color': 'transparent'
});

Root Cause

In src/components/titles/index.js - Line 163: The condition if(subtitleEnabled && subtitleElShouldExist) prevented D3's .exit().remove() from running when the subtitle needed to be cleared.

Additional changes

  • Line 84: Handle null/undefined subtitle.text
  • Lines 244 & 408: Add checks for empty D3 selections to prevent operations on selections with 0 elements

Notes

This follows the same D3 enter/exit pattern already used for the main title element (lines 147-157 in the same file).

@gvwilson gvwilson added community community contribution P2 considered for next cycle fix fixes something broken labels Oct 7, 2025
@gvwilson
Copy link
Contributor

gvwilson commented Oct 7, 2025

Thanks for the fix @cminn10 - we'll see if we can find a reviewer for the next release.

@emilykl
Copy link
Contributor

emilykl commented Oct 7, 2025

This bug is almost definitely my fault 🙂 Thank you @cminn10 for the very precise fix!

@cminn10 This looks great. One nit -- this fix doesn't actually cause the subtitle to be removed when setting Plotly.relayout(gd, {'title.subtitle': undefined}); (or Plotly.relayout(gd, {'title.subtitle.text': undefined});) but that's fine -- a general property of Plotly.js is that setting a value to undefined is treated as a noop (title behaves the same way). Setting Plotly.relayout(gd, {'title.subtitle.text': {}}); DOES work thanks to this PR.

I'd like to add a Jasmine test to prevent a regression. Can you add this test to test/jasmine/tests/titles_test.js?

describe('Subtitle clearing via relayout', function() {
    'use strict';

    var data = [{ x: [1, 2, 3], y: [1, 2, 3] }];
    var gd;

    beforeEach(function() {
        gd = createGraphDiv();
    });

    afterEach(destroyGraphDiv);

    it('should properly clear subtitle when set to null', function(done) {
        Plotly.newPlot(gd, data, {
            title: {
                text: 'Main Title',
                subtitle: { text: 'Subtitle Text' }
            }
        })
            .then(function() {
                var subtitleSel = d3Select('.gtitle-subtitle');
                expect(subtitleSel.empty()).toBe(false, 'Subtitle should exist initially');
                expect(subtitleSel.text()).toBe('Subtitle Text');

                return Plotly.relayout(gd, { 'title.subtitle.text': null });
            })
            .then(function() {
                var subtitleSel = d3Select('.gtitle-subtitle');
                expect(subtitleSel.empty()).toBe(true, 'Subtitle should be removed when set to null');
            })
            .then(done, done.fail);
    });

    it('should properly clear subtitle when set to empty string', function(done) {
        Plotly.newPlot(gd, data, {
            title: {
                text: 'Main Title',
                subtitle: { text: 'Subtitle Text' }
            }
        })
            .then(function() {
                var subtitleSel = d3Select('.gtitle-subtitle');
                expect(subtitleSel.empty()).toBe(false, 'Subtitle should exist initially');

                return Plotly.relayout(gd, { 'title.subtitle.text': '' });
            })
            .then(function() {
                var subtitleSel = d3Select('.gtitle-subtitle');
                expect(subtitleSel.empty()).toBe(true, 'Subtitle should be removed when set to empty string');
            })
            .then(done, done.fail);
    });

    it('should properly clear subtitle when set to whitespace', function(done) {
        Plotly.newPlot(gd, data, {
            title: {
                text: 'Main Title',
                subtitle: { text: 'Subtitle Text' }
            }
        })
            .then(function() {
                var subtitleSel = d3Select('.gtitle-subtitle');
                expect(subtitleSel.empty()).toBe(false, 'Subtitle should exist initially');

                return Plotly.relayout(gd, { 'title.subtitle.text': '   ' });
            })
            .then(function() {
                var subtitleSel = d3Select('.gtitle-subtitle');
                expect(subtitleSel.empty()).toBe(true, 'Subtitle should be removed when set to whitespace');
            })
            .then(done, done.fail);
    });
});

Copy link
Contributor

@emilykl emilykl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Thanks @cminn10 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community community contribution fix fixes something broken P2 considered for next cycle
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants